package com.lt.flashcard.model.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.bson.types.ObjectId;
import org.hibernate.Criteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.zkoss.xel.fn.StringFns;

import com.google.code.morphia.dao.BasicDAO;
import com.google.code.morphia.query.Query;
import com.lt.flashcard.Utility;
import com.lt.flashcard.model.entity.Flashcard;
import com.lt.flashcard.model.entity.FlashcardSet;
import com.lt.flashcard.model.entity.Journey;
import com.lt.flashcard.model.entity.JourneyHistory;
import com.lt.flashcard.model.entity.JourneyItem;
import com.lt.flashcard.model.entity.Learnhistory;
import com.lt.flashcard.model.entity.Medal;
import com.lt.flashcard.model.entity.MedalHistory;
import com.lt.flashcard.model.entity.Promotion;
import com.lt.flashcard.model.entity.Testcase;
import com.lt.flashcard.model.entity.TestcaseMatchingData;
import com.lt.flashcard.model.entity.Testsuite;
import com.lt.flashcard.model.entity.User;

public class LearningService {

    private static final Logger log = Logger.getLogger(LearningService.class);

    /**
     * Return a list of card for learning and follow the rule 0, 1, 7
     * 
     * @param flashcardset
     * @return
     */
    public List<Flashcard> buildLearningOrder(FlashcardSet flashcardset) {
        List<Flashcard> rs = new ArrayList<Flashcard>();
        for (Flashcard fc : flashcardset.getFlashcards()) {
            if (fc.getLearnLevel() == Flashcard.LEARNLEVEL_NEW)
                rs.add(fc);
        }
        return rs;
    }

    /**
     * Return a list of flashcard for learnt
     * 
     * @param flashcardset
     * @return
     */
    public List<Flashcard> buildLearntOrder(FlashcardSet flashcardset) {
        List<Flashcard> rs = new ArrayList<Flashcard>();
        for (Flashcard fc : flashcardset.getFlashcards())
            if (fc.getLearnLevel() > Flashcard.LEARNLEVEL_NEW)
                rs.add(fc);
        return rs;
    }

    /**
     * Get DAO to interact with testsuite data
     * 
     * @return
     */
    private BasicDAO<Testsuite, ObjectId> getTestsuiteDAO() {
        return new BasicDAO<Testsuite, ObjectId>(Testsuite.class, Repository.getInstance().getDatastore());
    }

    /**
     * Get DAO to interact with learnhistory
     * 
     * @return
     */
    private com.lt.flashcard.model.service.BasicDAO<Learnhistory> getLearnhistoryDAO() {
        return new com.lt.flashcard.model.service.BasicDAO<Learnhistory>(Learnhistory.class);
    }

    private BasicDAO<Journey, ObjectId> _journeyDAO = null;

    private BasicDAO<Journey, ObjectId> getJourneyDAO() {
        if (_journeyDAO == null)
            _journeyDAO = new BasicDAO<Journey, ObjectId>(Journey.class, Repository.getInstance().getDatastore());
        return _journeyDAO;
    }

    private BasicDAO<JourneyHistory, ObjectId> _journeyhistoryDAO = null;

    private BasicDAO<JourneyHistory, ObjectId> getJourneyHistoryDAO() {
        if (_journeyhistoryDAO == null)
            _journeyhistoryDAO = new BasicDAO<JourneyHistory, ObjectId>(JourneyHistory.class, Repository.getInstance().getDatastore());
        return _journeyhistoryDAO;
    }

    private BasicDAO<Medal, ObjectId> _medalDAO = null;

    private BasicDAO<Medal, ObjectId> getMedalDAO() {
        if (_medalDAO == null)
            _medalDAO = new BasicDAO<Medal, ObjectId>(Medal.class, Repository.getInstance().getDatastore());
        return _medalDAO;
    }

    private BasicDAO<MedalHistory, ObjectId> _medalhistoryDAO = null;

    private BasicDAO<MedalHistory, ObjectId> getMedalhistoryDAO() {
        if (_medalhistoryDAO == null)
            _medalhistoryDAO = new BasicDAO<MedalHistory, ObjectId>(MedalHistory.class, Repository.getInstance().getDatastore());
        return _medalhistoryDAO;
    }

    /**
     * Get journey
     * 
     * @param id
     * @return
     */
    public Journey getJourney(String id) {
        return getJourneyDAO().get(new ObjectId(id));
    }

    /**
     * Get journey by name
     * 
     * @param name
     * @return
     */
    public Journey getJourneyByName(String name) {
        BasicDAO<Journey, ObjectId> dao = getJourneyDAO();
        Query<Journey> query = dao.createQuery();
        query.field("name").equal(name);
        return query.get();
    }

    /**
     * Get all journeys in system
     * 
     * @return
     */
    public List<Journey> getAllJourneys() {
        BasicDAO<Journey, ObjectId> dao = getJourneyDAO();
        Query<Journey> query = dao.createQuery();
        query.field("status").equal(Journey.STATUS_VALID);
        query.order("order");
        return query.asList();
    }

    /**
     * Save journey
     * 
     * @param journey
     */
    public void saveJourney(Journey journey) {
        getJourneyDAO().save(journey);
    }

    /**
     * Delete journey
     * 
     * @param journey
     */
    public void deleteJourney(Journey journey) {
        try {
            Repository repo = Repository.getInstance();
            /*
             * repo.deleteBinaryData(journey.getNameThumbnail());
             * repo.deleteBinaryData(journey.getDashboardBackground());
             * repo.deleteBinaryData(journey.getProfileBackground());
             * repo.deleteBinaryData(journey.getLargeThumbnail()); repo.deleteBinaryData(journey.getSmallThumbnail());
             * 
             * // delete images in each item for (JourneyItem item : journey.getItems()) {
             * repo.deleteBinaryData(item.getSmallThumbnail()); repo.deleteBinaryData(item.getSmallGrayThumbnail());
             * repo.deleteBinaryData(item.getJourneyImage()); repo.deleteBinaryData(item.getDashboardImage()); }
             */

            // delete history
            List<JourneyHistory> lst = getJourneyHistoryOfJourney(journey);
            for (JourneyHistory h : lst) {
                deleteJourneyHistory(h);
            }

            getJourneyDAO().delete(journey);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Find journeyhistory by id
     * 
     * @param id
     * @return
     */
    public JourneyHistory getJourneyHistory(String id) {
        return getJourneyHistoryDAO().get(new ObjectId(id));
    }

    /**
     * Get all history of a specific journey
     * 
     * @param journey
     * @return
     */
    public List<JourneyHistory> getJourneyHistoryOfJourney(Journey journey) {
        BasicDAO<JourneyHistory, ObjectId> dao = getJourneyHistoryDAO();
        Query<JourneyHistory> query = dao.createQuery();
        query.field("journey").equal(journey);
        return query.asList();
    }

    /**
     * Get journey history of a userid for a specific journey
     * 
     * @param userid
     * @param journey
     * @return
     */
    public JourneyHistory getJourneyHistoryOfUser(Long userid, Journey journey) {
        Query<JourneyHistory> query = getJourneyHistoryDAO().createQuery();
        query.field("userid").equal(userid);
        query.field("journey").equal(journey);
        return query.get();
    }

    /**
     * Count total number of journey that a user attended
     * 
     * @param userid
     * @return
     */
    public long countJourneyOfUser(Long userid) {
        Query<JourneyHistory> query = getJourneyHistoryDAO().createQuery();
        query.field("userid").equal(userid);
        return query.countAll();
    }

    /**
     * Count number of journey item of a user
     * 
     * @param userid
     * @return
     */
    public int countJourneyItemsOfUser(Long userid) {
        Query<JourneyHistory> query = getJourneyHistoryDAO().createQuery();
        query.field("userid").equal(userid);

        int rs = 0;
        for (JourneyHistory his : query.asList())
            rs += his.getItemHistory().size();

        return rs;
    }

    /**
     * Create new JourneyHistory
     * 
     * @param userid
     * @param journey
     * @return
     */
    public JourneyHistory newJourneyHistory(Long userid, Journey journey) {
        JourneyHistory his = new JourneyHistory();
        his.setJourney(journey);
        his.setUserid(userid);
        return his;
    }

    /**
     * Save journey history
     * 
     * @param history
     */
    public void saveJourneyHistory(JourneyHistory history) {
        if (history.getUserid() == null || history.getUserid().equals(0) || history.getJourney() == null) {
            return;
        }
        if (history.getId() == null) {// this is begin route
            UserService usrv = Repository.getInstance().getUserService();
            usrv.addActivityBeginJourney(usrv.findByID(history.getUserid()), history.getJourney());
        }
        getJourneyHistoryDAO().save(history);
    }

    /**
     * Delete journey history
     * 
     * @param history
     */
    public void deleteJourneyHistory(JourneyHistory history) {
        getJourneyHistoryDAO().delete(history);
    }

    /**
     * Get current journey of userid
     * 
     * @param userid
     * @return
     */
    public Journey getCurrentJourney(Long userid) {
        Query<JourneyHistory> query = getJourneyHistoryDAO().createQuery();
        query.field("userid").equal(userid);
        query.field("status").equal(JourneyHistory.STATUS_STARTED);
        query.limit(1);
        JourneyHistory his = query.get();
        return his == null ? null : his.getJourney();
    }

    /**
     * Check if user can get an item or not
     * 
     * @param user
     * @param journey
     * @param index
     * @return -1 for already, 0 for not enough condition, 1 for ready, 2 for locked, 3 for not enough coin
     */
    public int checkCanGetItem(User user, Journey journey, int index) {
        JourneyHistory his = journey.getHistory(user);
        JourneyItem item = journey.getItems().get(index);
        if (his == null || !his.isStarted())
            return 0;
        if (index < his.getItemHistory().size())
            return -1;
        if (index == his.getItemHistory().size() && user.getBlueCoin() >= item.getCoin())
            return 1;
        else if (index == his.getItemHistory().size())
            return 3;
        if (index > his.getItemHistory().size() && user.getBlueCoin() >= item.getCoin())
            return 2;
        return 0;
    }

    /**
     * User get an item
     * 
     * @param user
     * @param journey
     * @param index
     */
    public boolean getJourneyItem(User user, Journey journey, int index) {
        if (checkCanGetItem(user, journey, index) == 1) {
            UserService usrv = Repository.getInstance().getUserService();
            JourneyHistory his = journey.getHistory(user);
            JourneyItem item = journey.getItems().get(index);
            his.getItemHistory().add(index, new Date());
            his.setCurrentCoin(his.getCurrentCoin() + item.getCoin());
            user.setBlueCoin(user.getBlueCoin() - item.getCoin());
            usrv.addActivityGetItem(user, item);

            // check to see if finish the journey
            if (index == journey.getItems().size() - 1) {
                his.setStatus(JourneyHistory.STATUS_FINISHED);
                his.setFinished(new Date());
            }

            usrv.save(user);
            saveJourneyHistory(his);
            return true;
        }
        return false;
    }

    /**
     * Find a testsuite by id
     * 
     * @param id
     *            The id of testsuite
     * @return
     */
    public Testsuite getTestsuiteById(String id) {
        return getTestsuiteDAO().get(new ObjectId(id));
    }

    /**
     * Save test suite data to database
     * 
     * @param testsuite
     */
    public void saveTestsuite(Testsuite testsuite) {
        getTestsuiteDAO().save(testsuite);
    }

    /**
     * Delete a testsuite data from database
     * 
     * @param testsuite
     */
    public void deleteTestsuite(Testsuite testsuite) {
        getTestsuiteDAO().delete(testsuite);
    }

    /**
     * Generate testsuite for new testing
     * 
     * @param flashcardset
     *            The flashcardset to generate
     * @param type
     *            The testing type, can be learned words or all words
     * @return
     * @throws Exception
     */
    public Testsuite generateTestsuite(FlashcardSet flashcardset, int type) throws Exception {
        if (log.isInfoEnabled())
            log.info("Generate testsuite for " + flashcardset.getId() + " type " + type);
        // find the target flashcards
        List<Flashcard> fcs;
        Date now = new Date();
        String nowStr = Utility.formatDate(now);
        if (type == Testsuite.TYPE_TESTALL)
            fcs = new ArrayList<Flashcard>(flashcardset.getFlashcards());
        else {
            fcs = new ArrayList<Flashcard>();
            for (Flashcard fc : flashcardset.getFlashcards()) {
                if (fc.getLearnLevel() == Flashcard.LEARNLEVEL_FINISH && Utility.formatDate(fc.getLasttestdate()).equals(nowStr))
                    fcs.add(fc);
                else if (fc.getLearnLevel() != Flashcard.LEARNLEVEL_FINISH && fc.getLearnLevel() != Flashcard.LEARNLEVEL_NEW) {
                    if ((fc.getLearnNextdate() != null && fc.getLearnNextdate().before(now))
                            || (fc.getLasttestdate() != null && Utility.formatDate(fc.getLasttestdate()).equals(nowStr)))
                        fcs.add(fc);
                }
            }
        }

        if (log.isInfoEnabled()) {
            String id = "";
            for (Flashcard fc : fcs)
                id += String.valueOf(fc.getTerm()) + "|";
            log.info("Generate for fc: " + id);
        }

        Testsuite testsuite = new Testsuite();// The result testsuite
        testsuite.setName(flashcardset.getName());
        testsuite.setType(type == Testsuite.TYPE_TESTLEARNED ? Testsuite.TYPE_TESTLEARNED : Testsuite.TYPE_TESTALL);
        testsuite.setCreated(new Date());
        testsuite.setUid(flashcardset.getAuthor().getId());
        testsuite.setFlashcardsetid(flashcardset.getId());

        int maxtime = 0;
        int maxscore = 0;
        int totalwords = 0;
        int typing = 0;
        int matching = 0;
        int multi = 0;
        List<Testcase> tcs = new ArrayList<Testcase>();
        Set<Long> used = new HashSet<Long>(); // Store list of id that used for testing
        for (Flashcard fc : fcs) {

            if (used.contains(fc.getId())) // in case of test matching, can use up to 3 cards
                continue;

            Testcase tcase;

            // test
            /*
             * tcase = generateTestMultiChoice(fc, flashcardset); typing += 1; used.add(fc.getId());
             */

            boolean isSameDate = false;
            int testtype = Testcase.TYPE_MULTICHOICE;
            if (type == Testsuite.TYPE_TESTALL) {
                testtype = Testcase.TYPE_TYPING;
            } else {
                if (fc.getLasttestdate() != null && Utility.formatDate(fc.getLasttestdate()).equals(nowStr)) {// Test in
                                                                                                              // same
                                                                                                              // date
                    switch (fc.getLasttesttype()) {
                    case Testcase.TYPE_MULTICHOICE:
                    case Testcase.TYPE_MATCHING:
                    case Testcase.TYPE_TYPING:
                        testtype = fc.getLasttesttype();
                        break;
                    default:
                        testtype = Testcase.TYPE_MULTICHOICE;
                        break;
                    }
                    isSameDate = true;
                } else {// normal test
                    switch (fc.getLearnLevel()) {
                    case Flashcard.LEARNLEVEL_0:
                        testtype = Testcase.TYPE_MULTICHOICE;
                        break;
                    case Flashcard.LEARNLEVEL_1:
                        testtype = Testcase.TYPE_MATCHING;
                        break;
                    case Flashcard.LEARNLEVEL_7_1:
                    case Flashcard.LEARNLEVEL_7:
                        testtype = Testcase.TYPE_TYPING;
                        break;
                    default:
                        testtype = Testcase.TYPE_TYPING;
                    }
                }
            }

            switch (testtype) {
            case Testcase.TYPE_MULTICHOICE:
                tcase = generateTestMultiChoice(fc, flashcardset);
                multi += 1;
                used.add(fc.getId());
                tcase.setSameDate(isSameDate);
                break;
            case Testcase.TYPE_MATCHING:
                tcase = generateTestMatching(fc, fcs);
                used.add(tcase.getMatching0().getFlashcardid());
                if (tcase.getTotalwords() >= 2)
                    used.add(tcase.getMatching1().getFlashcardid());
                if (tcase.getTotalwords() >= 3)
                    used.add(tcase.getMatching2().getFlashcardid());
                matching += 1;
                tcase.setSameDate(isSameDate);
                break;
            default:
                tcase = generateTestTyping(fc);
                typing += 1;
                used.add(fc.getId());
                tcase.setSameDate(isSameDate);
                break;
            }

            tcs.add(tcase);
            maxtime += tcase.getTotaltime();
            maxscore += tcase.getMaxscore();
            totalwords += tcase.getTotalwords();
        }

        testsuite.setTestcases(tcs);
        testsuite.setTotalquestion(testsuite.getTestcases().size());
        testsuite.setMaxtime(maxtime);
        testsuite.setMaxscore(maxscore);
        testsuite.setTotalmatching(matching);
        testsuite.setTotalmultichoice(multi);
        testsuite.setTotaltyping(typing);
        testsuite.setTotalwords(totalwords);

        return testsuite;
    }

    /**
     * Generate the test typing for flashcard
     * 
     * @param flashcard
     * @return
     */
    private Testcase generateTestTyping(Flashcard flashcard) {
        Testcase tc = new Testcase();
        tc.setTotaltime(flashcard.getTerm().length() / 2 + 20);
        tc.setMaxscore(50);
        tc.setTesttype(Testcase.TYPE_TYPING);
        tc.setFlashcardid(flashcard.getId());
        tc.setTypingImage(flashcard.getImage());

        // generate the sentence
        tc.setTypingTerm(flashcard.getTerm());

        String[] types = new String[] { "definition", "note" };
        int ran = (int) (Math.random() * types.length);
        String type = types[ran];

        if (type.equals(Testcase.TYPING_NOTE)
                && (Utility.isEmptyString(flashcard.getNotes()) || !flashcard.getNotes().contains(flashcard.getTerm())))
            type = Testcase.TYPING_DEFINITION;

        if (type.equals(Testcase.TYPING_DEFINITION))
            tc.setTypingSentence(flashcard.getDefinition().replaceAll(flashcard.getTerm(), "_____________"));
        else if (type.equals(Testcase.TYPING_NOTE))
            tc.setTypingSentence(flashcard.getNotes().replaceAll(flashcard.getTerm(), "_____________"));

        tc.setTotalwords(1);
        return tc;
    }

    /**
     * Generate the test matching for flashcard
     * 
     * @param flashcard
     * @param flashcards
     *            list of flashcards
     * @return
     * @throws Exception
     */
    private Testcase generateTestMatching(Flashcard flashcard, List<Flashcard> flashcards) throws Exception {
        if (log.isInfoEnabled())
            log.info("Generate test matching for " + flashcard.getTerm());

        String strNow = Utility.formatDate(new Date());
        Date dateNow = new Date();
        Testcase tc = new Testcase();
        tc.setTotaltime(20);
        tc.setTesttype(Testcase.TYPE_MATCHING);
        tc.setFlashcardid(flashcard.getId());

        List<Flashcard> right = new ArrayList<Flashcard>();
        right.add(flashcard);

        // select 2 more
        int index = flashcards.indexOf(flashcard);
        if (index + 1 < flashcards.size()) {
            for (int i = index + 1; i < flashcards.size(); i++) {
                if ((flashcards.get(i).getLearnLevel() == Flashcard.LEARNLEVEL_1 && (strNow.equals(Utility.formatDate(flashcards.get(i)
                        .getLearnNextdate())) || dateNow.after(flashcards.get(i).getLearnNextdate())))
                        || (flashcards.get(i).getLasttestdate() != null
                                && strNow.equals(Utility.formatDate(flashcards.get(i).getLasttestdate())) && flashcards.get(i)
                                .getLasttesttype() == Flashcard.LEARNLEVEL_1)) {
                    right.add(flashcards.get(i));
                    if (right.size() >= 3)
                        break;
                }
            }
        }

        tc.setTotalwords(right.size());
        if (log.isInfoEnabled()) {
            String name = "";
            for (Flashcard fc : right)
                name += fc.getTerm() + "|";
            log.info("Right: " + name);
        }

        boolean onemore = false;

        if (right.size() < 3) {
            FlashcardService fcsrv = Repository.getInstance().getFlashcardService();
            Criteria fccrit = fcsrv.createCriteria();
            fccrit.add(Restrictions.ne("admin", FlashcardSet.ADMIN_BUY));// blueup card or normal
            fccrit.add(Restrictions.ne("flashcardSet", flashcard.getFlashcardSet()));
            fccrit.add(Restrictions.ne("definition", ""));
            fccrit.setMaxResults(12);
            List<Flashcard> lst = fccrit.list();

            if (log.isInfoEnabled()) {
                String name = "";
                for (Flashcard fc : lst)
                    name += fc.getTerm() + "," + fc.getId() + "|";
                log.info("Right less than 3, select more to random: " + name);
            }

            List<Integer> generated = new ArrayList<Integer>();
            int ran, maxsize = right.size();
            for (int i = 0; i < 4 - maxsize; i++) {
                do {
                    ran = (int) (Math.random() * lst.size());
                } while (generated.contains(ran));
                generated.add(ran);
                Flashcard fcsample = lst.get(ran);
                right.add(fcsample);

                if (log.isInfoEnabled())
                    log.info("Random select: " + fcsample.getTerm());
            }
        } else {
            // add one more
            if (flashcards.size() > right.size()) {
                for (Flashcard fc : flashcards) {
                    if (fc.getLearnLevel() == Flashcard.LEARNLEVEL_1
                            || (flashcard.getLasttestdate() != null && strNow.equals(Utility.formatDate(flashcard.getLasttestdate())) && flashcard
                                    .getLasttesttype() == Flashcard.LEARNLEVEL_1)) {
                        // NOP
                    } else {
                        if (!Utility.isEmptyString(fc.getDefinition())) {
                            right.add(fc);
                            onemore = true;

                            if (log.isInfoEnabled())
                                log.info("Select one " + fc.getTerm());
                            break;
                        }
                    }
                }
            }

            if (!onemore) {
                FlashcardService fcsrv = Repository.getInstance().getFlashcardService();
                Criteria fccrit = fcsrv.createCriteria();
                fccrit.add(Restrictions.ne("admin", FlashcardSet.ADMIN_BUY));// blueup card or normal
                fccrit.add(Restrictions.ne("flashcardSet", flashcard.getFlashcardSet()));
                fccrit.add(Restrictions.ne("definition", ""));
                fccrit.setMaxResults(12);
                List<Flashcard> lst = fccrit.list();

                int ran = (int) (Math.random() * lst.size());
                Flashcard m = lst.get(ran);
                right.add(m);
                if (log.isInfoEnabled())
                    log.info("Random one " + m.getTerm());
                onemore = true;
            }
        }

        if (right.size() != 4)
            throw new Exception("Not enough card");

        int turn = 0;
        for (Flashcard fc : right) {
            TestcaseMatchingData fcd = new TestcaseMatchingData();
            fcd.setTerm(fc.getTerm());
            fcd.setFlashcardid(fc.getId());
            fcd.setDefinition(fc.getDefinition());
            fcd.setImage(fc.getImage());

            if (turn < tc.getTotalwords()) {
                if (fc.getLearnLevel() != Flashcard.LEARNLEVEL_1 && flashcard.getLasttestdate() != null
                        && strNow.equals(Utility.formatDate(flashcard.getLasttestdate()))
                        && flashcard.getLasttesttype() == Flashcard.LEARNLEVEL_1) {
                    fcd.setSameDate(true);
                }
            }

            if (turn == 0)
                tc.setMatching0(fcd);
            else if (turn == 1)
                tc.setMatching1(fcd);
            else if (turn == 2)
                tc.setMatching2(fcd);
            else
                tc.setMatching3(fcd);
            turn++;
        }

        tc.setMaxscore(2 * 15 * tc.getTotalwords());// max 3 pairs, each pair gains 2 * 15 coins
        return tc;
    }

    /**
     * Generate the test multi choice for flashcard
     * 
     * @param flashcard
     * @return
     * @throws Exception
     */
    private Testcase generateTestMultiChoice(Flashcard flashcard, FlashcardSet fcs) throws Exception {
        Testcase tc = new Testcase();
        tc.setTotaltime(10);
        tc.setMaxscore(20);
        tc.setTesttype(Testcase.TYPE_MULTICHOICE);
        tc.setFlashcardid(flashcard.getId());
        tc.setMultiImage(flashcard.getImage());

        ArrayList<Flashcard> fc = new ArrayList<Flashcard>(fcs.getFlashcards());
        for (Flashcard f : fc) {
            if (f.getId().equals(flashcard.getId())) {
                fc.remove(f);
                break;
            }
        }
        FlashcardService fcsrv = Repository.getInstance().getFlashcardService();
        Criteria fccrit;

        if (fc.size() < 12) {// select random 12 others
            fccrit = fcsrv.createCriteria();
            fccrit.add(Restrictions.ne("flashcardSet", fcs));
            fccrit.add(Restrictions.ne("admin", FlashcardSet.ADMIN_YES));
            fccrit.addOrder(Order.asc("admin"));
            fccrit.setMaxResults(12);
            List<Flashcard> lst = fccrit.list();
            fc.addAll(lst);
        }

        if (fc.size() < 12)
            throw new Exception("Not enough flashcard");

        String[] genType;
        if (fcs.getAdmin() == FlashcardSet.ADMIN_NO)
            genType = new String[] { Testcase.MULTI_DEFINITION, Testcase.MULTI_NOTE, Testcase.MULTI_TERM };
        else
            // genType = new String[] { Testcase.MULTI_DEFINITION, Testcase.MULTI_NOTE, Testcase.MULTI_SYNONYMTERM,
            // Testcase.MULTI_TERM, Testcase.MULTI_TERMSYNONYM};
            genType = new String[] { Testcase.MULTI_DEFINITION, Testcase.MULTI_NOTE, Testcase.MULTI_TERM };

        // random select a type
        int ran = (int) (Math.random() * genType.length);
        String type = genType[ran];

        if (type.equals(Testcase.MULTI_NOTE)
                && (Utility.isEmptyString(flashcard.getNotes()) || !flashcard.getNotes().contains(flashcard.getTerm())))
            type = Testcase.MULTI_TERM;
        else if (type.equals(Testcase.MULTI_TERMSYNONYM) && Utility.isEmptyString(flashcard.getSymnonym()))
            type = Testcase.MULTI_TERM;
        else if (type.equals(Testcase.MULTI_SYNONYMTERM) && Utility.isEmptyString(flashcard.getSymnonym()))
            type = Testcase.MULTI_TERM;

        // test
        // type = Testcase.MULTI_TERM;

        tc.setMultiType(type);
        tc.setMultiImage(flashcard.getImage());

        if (type.equals(Testcase.MULTI_TERM)) { // gen by term
            tc.setMultiSentence(flashcard.getTerm());
            tc.setMultiImage(null);

            int turn = 0;
            List<Integer> generated = new ArrayList<Integer>();
            List<Object> defs = new ArrayList<Object>();
            String fcdef = Utility.isEmptyString(flashcard.getDefinition()) ? "image:".concat(flashcard.getImage()) : flashcard
                    .getDefinition();
            defs.add(fcdef + "|" + flashcard.getId().toString());
            do {
                do {
                    ran = (int) (Math.random() * fc.size());
                } while (generated.contains(ran));
                generated.add(ran);

                Flashcard fcsample = fc.get(ran);
                // test
                // defs.add("image:".concat(fcsample.getImage()));
                if (Utility.isEmptyString(fcsample.getDefinition()))
                    defs.add("image:".concat(fcsample.getImage()) + "|" + fcsample.getId().toString());
                else
                    defs.add(fcsample.getDefinition() + "|" + fcsample.getId().toString());
                turn += 1;
            } while (turn < 5);

            Object[] randomDefs = Utility.randomOrder(defs);
            tc.setMultiIndex(Arrays.asList(randomDefs).indexOf(fcdef + "|" + flashcard.getId().toString()));

            // assign
            String[] array = randomDefs[0].toString().split("\\|");
            tc.setMultiText0(array[0]);
            tc.setMultiId0(array[1]);
            array = randomDefs[1].toString().split("\\|");
            tc.setMultiText1(array[0]);
            tc.setMultiId1(array[1]);
            array = randomDefs[2].toString().split("\\|");
            tc.setMultiText2(array[0]);
            tc.setMultiId2(array[1]);
            array = randomDefs[3].toString().split("\\|");
            tc.setMultiText3(array[0]);
            tc.setMultiId3(array[1]);
            array = randomDefs[4].toString().split("\\|");
            tc.setMultiText4(array[0]);
            tc.setMultiId4(array[1]);
            array = randomDefs[5].toString().split("\\|");
            tc.setMultiText5(array[0]);
            tc.setMultiId5(array[1]);

            // tc.setMultiText0(randomDefs[0].toString());
            // tc.setMultiText1(randomDefs[1].toString());
            // tc.setMultiText2(randomDefs[2].toString());
            // tc.setMultiText3(randomDefs[3].toString());
            // tc.setMultiText4(randomDefs[4].toString());
            // tc.setMultiText5(randomDefs[5].toString());
        } else if (type.equals(Testcase.MULTI_DEFINITION)) {
            tc.setMultiSentence(flashcard.getDefinition());

            int turn = 0;
            List<Integer> generated = new ArrayList<Integer>();
            List<Object> defs = new ArrayList<Object>();
            defs.add(flashcard.getTerm() + "|" + flashcard.getId().toString());
            do {
                do {
                    ran = (int) (Math.random() * fc.size());
                } while (generated.contains(ran));
                generated.add(ran);

                Flashcard fcsample = fc.get(ran);
                defs.add(fcsample.getTerm() + "|" + fcsample.getId().toString());

                turn += 1;
            } while (turn < 5);

            Object[] randomDefs = Utility.randomOrder(defs);
            tc.setMultiIndex(Arrays.asList(randomDefs).indexOf(flashcard.getTerm() + "|" + flashcard.getId().toString()));

            // assign
            String[] array = randomDefs[0].toString().split("\\|");
            tc.setMultiText0(array[0]);
            tc.setMultiId0(array[1]);
            array = randomDefs[1].toString().split("\\|");
            tc.setMultiText1(array[0]);
            tc.setMultiId1(array[1]);
            array = randomDefs[2].toString().split("\\|");
            tc.setMultiText2(array[0]);
            tc.setMultiId2(array[1]);
            array = randomDefs[3].toString().split("\\|");
            tc.setMultiText3(array[0]);
            tc.setMultiId3(array[1]);
            array = randomDefs[4].toString().split("\\|");
            tc.setMultiText4(array[0]);
            tc.setMultiId4(array[1]);
            array = randomDefs[5].toString().split("\\|");
            tc.setMultiText5(array[0]);
            tc.setMultiId5(array[1]);

            // tc.setMultiText0(randomDefs[0].toString());
            // tc.setMultiText1(randomDefs[1].toString());
            // tc.setMultiText2(randomDefs[2].toString());
            // tc.setMultiText3(randomDefs[3].toString());
            // tc.setMultiText4(randomDefs[4].toString());
            // tc.setMultiText5(randomDefs[5].toString());
        } else if (type.equals(Testcase.MULTI_NOTE)) {
            tc.setMultiSentence(flashcard.getNotes().replaceAll("(?i)" + flashcard.getTerm(), "__________"));

            int turn = 0;
            List<Integer> generated = new ArrayList<Integer>();
            List<Object> defs = new ArrayList<Object>();
            defs.add(flashcard.getTerm() + "|" + flashcard.getId().toString());
            do {
                do {
                    ran = (int) (Math.random() * fc.size());
                } while (generated.contains(ran));
                generated.add(ran);

                Flashcard fcsample = fc.get(ran);
                defs.add(fcsample.getTerm() + "|" + fcsample.getId().toString());

                turn += 1;
            } while (turn < 5);

            Object[] randomDefs = Utility.randomOrder(defs);
            tc.setMultiIndex(Arrays.asList(randomDefs).indexOf(flashcard.getTerm() + "|" + flashcard.getId().toString()));

            // assign
            String[] array = randomDefs[0].toString().split("\\|");
            tc.setMultiText0(array[0]);
            tc.setMultiId0(array[1]);
            array = randomDefs[1].toString().split("\\|");
            tc.setMultiText1(array[0]);
            tc.setMultiId1(array[1]);
            array = randomDefs[2].toString().split("\\|");
            tc.setMultiText2(array[0]);
            tc.setMultiId2(array[1]);
            array = randomDefs[3].toString().split("\\|");
            tc.setMultiText3(array[0]);
            tc.setMultiId3(array[1]);
            array = randomDefs[4].toString().split("\\|");
            tc.setMultiText4(array[0]);
            tc.setMultiId4(array[1]);
            array = randomDefs[5].toString().split("\\|");
            tc.setMultiText5(array[0]);
            tc.setMultiId5(array[1]);

            // tc.setMultiText0(randomDefs[0].toString());
            // tc.setMultiText1(randomDefs[1].toString());
            // tc.setMultiText2(randomDefs[2].toString());
            // tc.setMultiText3(randomDefs[3].toString());
            // tc.setMultiText4(randomDefs[4].toString());
            // tc.setMultiText5(randomDefs[5].toString());
        } else if (type.equals(Testcase.MULTI_TERMSYNONYM)) {
            tc.setMultiSentence(flashcard.getTerm());

            int turn = 0;
            List<Integer> generated = new ArrayList<Integer>();
            List<Object> defs = new ArrayList<Object>();
            defs.add(flashcard.getSymnonym() + "|" + flashcard.getId().toString());
            do {
                do {
                    ran = (int) (Math.random() * fc.size());
                } while (generated.contains(ran));
                generated.add(ran);

                Flashcard fcsample = fc.get(ran);
                defs.add(fcsample.getTerm() + "|" + fcsample.getId().toString());

                turn += 1;
            } while (turn < 5);

            Object[] randomDefs = Utility.randomOrder(defs);
            tc.setMultiIndex(Arrays.asList(randomDefs).indexOf(flashcard.getSymnonym() + "|" + flashcard.getId().toString()));

            // assign
            String[] array = randomDefs[0].toString().split("\\|");
            tc.setMultiText0(array[0]);
            tc.setMultiId0(array[1]);
            array = randomDefs[1].toString().split("\\|");
            tc.setMultiText1(array[0]);
            tc.setMultiId1(array[1]);
            array = randomDefs[2].toString().split("\\|");
            tc.setMultiText2(array[0]);
            tc.setMultiId2(array[1]);
            array = randomDefs[3].toString().split("\\|");
            tc.setMultiText3(array[0]);
            tc.setMultiId3(array[1]);
            array = randomDefs[4].toString().split("\\|");
            tc.setMultiText4(array[0]);
            tc.setMultiId4(array[1]);
            array = randomDefs[5].toString().split("\\|");
            tc.setMultiText5(array[0]);
            tc.setMultiId5(array[1]);

            // tc.setMultiText0(randomDefs[0].toString());
            // tc.setMultiText1(randomDefs[1].toString());
            // tc.setMultiText2(randomDefs[2].toString());
            // tc.setMultiText3(randomDefs[3].toString());
            // tc.setMultiText4(randomDefs[4].toString());
            // tc.setMultiText5(randomDefs[5].toString());
        } else if (type.equals(Testcase.MULTI_SYNONYMTERM)) {
            tc.setMultiSentence(flashcard.getSymnonym());

            int turn = 0;
            List<Integer> generated = new ArrayList<Integer>();
            List<Object> defs = new ArrayList<Object>();
            defs.add(flashcard.getTerm() + "|" + flashcard.getId().toString());
            do {
                do {
                    ran = (int) (Math.random() * fc.size());
                } while (generated.contains(ran));
                generated.add(ran);

                Flashcard fcsample = fc.get(ran);
                defs.add(fcsample.getTerm() + "|" + fcsample.getId().toString());

                turn += 1;
            } while (turn < 5);

            Object[] randomDefs = Utility.randomOrder(defs);
            tc.setMultiIndex(Arrays.asList(randomDefs).indexOf(flashcard.getTerm() + "|" + flashcard.getId().toString()));

            // assign
            String[] array = randomDefs[0].toString().split("\\|");
            tc.setMultiText0(array[0]);
            tc.setMultiId0(array[1]);
            array = randomDefs[1].toString().split("\\|");
            tc.setMultiText1(array[0]);
            tc.setMultiId1(array[1]);
            array = randomDefs[2].toString().split("\\|");
            tc.setMultiText2(array[0]);
            tc.setMultiId2(array[1]);
            array = randomDefs[3].toString().split("\\|");
            tc.setMultiText3(array[0]);
            tc.setMultiId3(array[1]);
            array = randomDefs[4].toString().split("\\|");
            tc.setMultiText4(array[0]);
            tc.setMultiId4(array[1]);
            array = randomDefs[5].toString().split("\\|");
            tc.setMultiText5(array[0]);
            tc.setMultiId5(array[1]);

            // tc.setMultiText0(randomDefs[0].toString());
            // tc.setMultiText1(randomDefs[1].toString());
            // tc.setMultiText2(randomDefs[2].toString());
            // tc.setMultiText3(randomDefs[3].toString());
            // tc.setMultiText4(randomDefs[4].toString());
            // tc.setMultiText5(randomDefs[5].toString());
        }
        tc.setTotalwords(1);
        return tc;
    }

    /**
     * Delete learn history from database
     * 
     * @param learn
     */
    public void deleteLearnhistory(Learnhistory learn) {
        com.lt.flashcard.model.service.BasicDAO<Learnhistory> srv = getLearnhistoryDAO();
        srv.delete(learn);
    }

    /**
     * Save learn history to database
     * 
     * @param learn
     */
    public void saveLearnhistory(Learnhistory learn) {
        // update the caching in flashcard set
        FlashcardSet fcs = learn.getFlashcardset();
        String cache = fcs.getTestingresultCache();
        String thistest = Utility.formatDate(learn.getLearnOn(), "d/M") + "," + String.valueOf(learn.getResultrightwords()) + ","
                + String.valueOf(learn.getTotalwords() - learn.getResultrightwords());
        cache = StringUtils.isBlank(cache) ? thistest : cache + "|" + thistest;
        String[] arrCache = StringFns.split(cache, "\\|");
        if (arrCache.length > 5) {
            arrCache = Arrays.copyOfRange(arrCache, arrCache.length - 5, arrCache.length);
            fcs.setTestingresultCache(StringFns.join(arrCache, "|"));
        } else
            fcs.setTestingresultCache(cache);
        com.lt.flashcard.model.service.BasicDAO<Learnhistory> srv = getLearnhistoryDAO();
        srv.save(learn);
    }

    /**
     * Load learn history data from database by id
     * 
     * @param id
     * @return
     */
    public Learnhistory loadLearnhistory(Long id) {
        com.lt.flashcard.model.service.BasicDAO<Learnhistory> srv = getLearnhistoryDAO();
        return srv.findByID(id);
    }

    /**
     * Load learn history for a given flashcardset and type
     * 
     * @param flashcardset
     * @param type
     * @return
     */
    public Learnhistory loadLearnhistoryByType(FlashcardSet flashcardset, int type) {
        FlashcardSetService srv = Repository.getInstance().getFlashcardSetService();
        Criteria crit = srv.createCriteria();
        crit.add(Restrictions.eq("flashcardset", flashcardset));
        crit.add(Restrictions.eq("type", type));
        return (Learnhistory) crit.uniqueResult();
    }

    /**
     * Get latest test result of a user
     * 
     * @param user
     * @return
     */
    public Learnhistory getLatestTestresult(User user) {
        if (user == null)
            return null;

        com.lt.flashcard.model.service.BasicDAO<Learnhistory> dao = getLearnhistoryDAO();
        Criteria crit = dao.createCriteria();
        crit.add(Restrictions.eq("user", user));
        crit.addOrder(Order.desc("learnOn"));
        crit.setMaxResults(1);

        return (Learnhistory) crit.uniqueResult();
    }

    /**
     * Get next testing date for flashcardset
     * 
     * @param fcs
     * @return
     */
    public Date getNextTestDate(FlashcardSet fcs, Date from) {
        Date rs = null;
        for (Flashcard fc : fcs.getFlashcards()) {
            if (fc != null && fc.getLearnNextdate() != null && fc.getLearnNextdate().before(from)) {
                if (rs == null || rs.after(fc.getLearnNextdate()))
                    rs = fc.getLearnNextdate();
            }
        }
        return rs;
    }

    /**
     * Save test suite and update learn history
     * 
     * @param testsuite
     * @return
     */
    public Learnhistory saveTestresult(Testsuite testsuite) {
        // save test suite
        saveTestsuite(testsuite);
        String tsid = testsuite.getId().toString();

        com.lt.flashcard.model.service.BasicDAO<Learnhistory> lsrv = getLearnhistoryDAO();
        Criteria crit = lsrv.createCriteria();
        crit.add(Restrictions.eq("dataid", tsid));
        Learnhistory his = (Learnhistory) crit.uniqueResult();

        UserService usrv = Repository.getInstance().getUserService();
        User user = usrv.findByID(testsuite.getUid());

        if (his == null) {
            FlashcardSetService fcsrv = Repository.getInstance().getFlashcardSetService();
            his = new Learnhistory();
            his.setUser(user);
            his.setFlashcardset(fcsrv.findByID(testsuite.getFlashcardsetid()));
            his.setLearnOn(new Date());
            his.setDataid(tsid);
        }

        his.setType(testsuite.getType());
        his.setTotalquestion(testsuite.getTotalquestion());
        his.setTotalmultichoice(testsuite.getTotalmultichoice());
        his.setTotalmatching(testsuite.getTotalmatching());
        his.setTotaltyping(testsuite.getTotaltyping());
        if (testsuite.getResultscore() >= 0)
            his.setResultscore(testsuite.getResultscore());
        else {
            his.setResultscore(0);
            log.error("Result score less than 0 for testsuite " + testsuite.getId() + ", real value: " + testsuite.getResultscore());
        }
        his.setResulttime(testsuite.getResulttime());
        his.setTotalwords(testsuite.getTotalwords());
        his.setResultrightwords(testsuite.getResultrightwords());

        // calculate the series of testing, to apply promotion
        Learnhistory lt = getLatestTestresult(user);
        if (lt != null) {
            Date d1 = lt.getLearnOn();
            d1.setTime(d1.getTime() + 1000 * 86400);
            Date d2 = his.getLearnOn();
            String t1 = Utility.formatDate(d1);
            String t2 = Utility.formatDate(d2);
            if (t1.equals(t2))
                his.setSeriesindex(lt.getSeriesindex() + 1);
            else if (d1.after(d2))
                his.setSeriesindex(lt.getSeriesindex());
        }

        saveLearnhistory(his);

        // update data to user
        // user.setBlueCoin(user.getBlueCoin() + testsuite.getResultscore());
        // user.setTotalBlueCoin(user.getTotalBlueCoin() + testsuite.getResultscore());
        // Update coin when finish each question

        // apply promotion
        EcommerceService esrv = Repository.getInstance().getEcommerceService();
        esrv.applyPromotions(user, Promotion.TARGETTYPE_USER, Promotion.CONDITIONTYPE_TESTCONTINUOUS, his.getSeriesindex(), true);

        usrv.save(user);
        return his;
    }

    public void updateKnowFlashcard(Long flashcardid) {
        FlashcardService srv = Repository.getInstance().getFlashcardService();
        setFlashcardAsKnow(srv.findByID(flashcardid));
    }

    /**
     * Set a word as knew and no need to learn
     * 
     * @param flashcard
     */
    public void setFlashcardAsKnow(Flashcard flashcard) {
        if (flashcard != null) {
            FlashcardService srv = Repository.getInstance().getFlashcardService();
            flashcard.setLearnNextdate(new Date());
            flashcard.setLearnLevel(Flashcard.LEARNLEVEL_7);
            flashcard.setHasLearnt(Flashcard.LEARN_HASLEARNT);
            flashcard.setLearnStatus(Flashcard.LEARNSTATUS_FINISH);
            srv.save(flashcard);
        }
    }

    /**
     * Set flashcard as learnt when click on I knew this, and ready for testing
     * 
     * @param id
     */
    public void setFlashcardAsLearnt(Flashcard card) {
        if (card != null) {
            FlashcardService srv = Repository.getInstance().getFlashcardService();
            card.setLearnLevel(Flashcard.LEARNLEVEL_0);
            card.setLearnNextdate(new Date());
            card.setTestStatus(Flashcard.TESTSTATUS_NOTYET);
            srv.save(card);
        }
    }

    /**
     * Set flashcard as not learn
     * 
     * @param id
     */
    public void setFlashcardAsNotLearnt(Flashcard card) {
        if (card != null) {
            FlashcardService srv = Repository.getInstance().getFlashcardService();
            card.setLearnLevel(Flashcard.LEARNLEVEL_NEW);
            card.setLearnNextdate(null);
            card.setTestStatus(Flashcard.TESTSTATUS_NOTYET);
            srv.save(card);
        }
    }

    /**
     * Set a flashcard as unknow and remove all previous testing
     * 
     * @param flashcardid
     * @return Flashcard target flashcard
     */
    public Flashcard setFlashcardAsUnknow(Long flashcardid) {
        FlashcardService srv = Repository.getInstance().getFlashcardService();
        Flashcard fc = srv.findByID(flashcardid);
        if (fc != null) {
            setFlashcardAsUnknow(fc);
            return fc;
        } else
            log.error("Can't find flashcardid " + flashcardid + " to set as unknow card");
        return null;
    }

    /**
     * Unknow, and reset the learning status to begining
     * 
     * @param flashcard
     */
    public void setFlashcardAsUnknow(Flashcard flashcard) {
        if (flashcard != null) {
            FlashcardService srv = Repository.getInstance().getFlashcardService();
            flashcard.setLearnNextdate(null);
            flashcard.setLearnLevel(Flashcard.LEARNLEVEL_NEW);
            flashcard.setTestStatus(Flashcard.TESTSTATUS_NOTYET);
            // TODO update statistic for user and flashcardset
            srv.save(flashcard);
        }
    }

    /**
     * A flashcard has passed the test for that level
     * 
     * @param flashcardid
     * @param isTestAll
     *            is in test all mode
     * @param testtype
     * @throws Exception
     */
    public void setFlashcardPassTest(Long flashcardid, boolean isTestAll, Testcase testcase) throws Exception {
        FlashcardService srv = Repository.getInstance().getFlashcardService();
        Flashcard fc = srv.findByID(flashcardid);
        if (fc != null) {
            // update user status
            UserService usrv = Repository.getInstance().getUserService();
            User user = Utility.getUser();
            int know = user.getKnowCards();
            int unknow = user.getUnknowCards();
            int nc = user.getNewCards();
            // TODO check this
            if (fc.getTestStatus() == Flashcard.TESTSTATUS_NOTYET) {
                nc -= 1;
                know += 1;
            } else if (fc.getTestStatus() == Flashcard.TESTSTATUS_FAIL) {
                know += 1;
                unknow -= 1;
            }

            user.setKnowCards(know > 0 ? know : 0);
            user.setUnknowCards(unknow > 0 ? unknow : 0);
            user.setNewCards(nc > 0 ? nc : 0);

            int lastTestStatus = fc.getTestStatus();
            if (isTestAll) {// Typing
                fc.setLearnNextdate(null);
                if (fc.getLearnLevel() == Flashcard.LEARNLEVEL_FINISH) {// nothing happens
                    if (testcase.getResulttime() > fc.getTestresult7())
                        fc.setTestresult7(testcase.getResulttime());

                    testcase.setResultscore(0); // already test level 7 before
                    testcase.setMakeupPoint(true);
                } else {
                    if (fc.getTestresult7() <= 0)
                        user.setKnowCards7(user.getKnowCards7() + 1);

                    fc.setLearnLevel(Flashcard.LEARNLEVEL_FINISH);
                    if (fc.getTestresult7() > 0) {// already test
                        testcase.setResultscore(0);
                        testcase.setMakeupPoint(true);
                        if (testcase.getResulttime() > fc.getTestresult7())
                            fc.setTestresult7(testcase.getResulttime());
                    } else {
                        fc.setTestresult7(testcase.getResulttime());
                    }
                }
            } else {
                if (testcase.getTesttype() == Testcase.TYPE_MATCHING) {
                    TestcaseMatchingData d = null;
                    if (fc.getId().equals(testcase.getMatching0().getFlashcardid()))
                        d = testcase.getMatching0();
                    else if (fc.getId().equals(testcase.getMatching1().getFlashcardid()))
                        d = testcase.getMatching1();
                    else if (fc.getId().equals(testcase.getMatching2().getFlashcardid()))
                        d = testcase.getMatching2();

                    if (d == null) {
                        log.error("Matching error");
                        throw new Exception("Matching error");
                    }

                    Date cur = new Date();
                    Calendar c = Calendar.getInstance();
                    c.setTime(cur);
                    c.add(Calendar.DATE, 7);
                    fc.setLearnNextdate(c.getTime());

                    if (d.isSameDate()) {
                        int dt = testcase.getResulttime() - fc.getTestresult1();

                        if (dt > 0) { // better
                            fc.setTestresult1(testcase.getResulttime());
                            d.setResultScore(dt * 2);
                        } else {
                            dt = 0;
                            d.setResultScore(0);
                        }
                        testcase.setResultscore(testcase.getResultscore() - testcase.getResulttime() * 2 + d.getResultScore());
                        d.setMakeupPoint(true);

                        if (lastTestStatus == Flashcard.TESTSTATUS_FAIL && fc.getLearnLevel() == Flashcard.LEARNLEVEL_0)
                            fc.setLearnLevel(Flashcard.LEARNLEVEL_7);
                        testcase.setMakeupPoint(true);
                    } else {
                        int dt = testcase.getResulttime() - fc.getTestresult1();
                        fc.setLearnLevel(Flashcard.LEARNLEVEL_7);

                        if (fc.getTestresult1() > 0) {
                            testcase.setMakeupPoint(true);
                            d.setMakeupPoint(true);
                        }
                        if (dt > 0) {
                            fc.setTestresult1(d.getResultTime());
                            d.setResultScore(dt * 2);
                        } else {
                            fc.setTestresult1(0);
                            d.setResultScore(0);
                        }
                        testcase.setResultscore(testcase.getResultscore() - testcase.getResulttime() * 2 + d.getResultScore());
                        user.setKnowCards1(user.getKnowCards1() + 1);
                    }
                } else if (testcase.isSameDate()) {// Do test in same date
                    int prevtime = 0;
                    switch (testcase.getTesttype()) {
                    case Testcase.TYPE_MULTICHOICE:
                        prevtime = fc.getTestresult0();
                        break;
                    case Testcase.TYPE_MATCHING:
                        prevtime = fc.getTestresult1();
                        break;
                    case Testcase.TYPE_TYPING:
                        prevtime = fc.getTestresult7();
                        break;
                    }
                    int dt = testcase.getResulttime() - prevtime;
                    if (dt > 0) {// better
                        switch (testcase.getTesttype()) {
                        case Testcase.TYPE_MULTICHOICE:
                            fc.setTestresult0(testcase.getResulttime());
                            if (testcase.isMultiTyping())
                                testcase.setResultscore(dt + dt);
                            else
                                testcase.setResultscore(dt);
                            break;
                        case Testcase.TYPE_MATCHING:// see above
                            fc.setTestresult1(testcase.getResulttime());
                            break;
                        case Testcase.TYPE_TYPING:
                            fc.setTestresult7(testcase.getResulttime());
                            testcase.setResultscore(0);
                            break;
                        }
                    } else {
                        testcase.setResultscore(0);
                    }
                    if (lastTestStatus == Flashcard.TESTSTATUS_FAIL) {
                        Date cur = new Date();
                        Calendar c = Calendar.getInstance();

                        switch (fc.getLearnLevel()) {
                        case Flashcard.LEARNLEVEL_NEW:
                            fc.setLearnLevel(Flashcard.LEARNLEVEL_1);
                            c.setTime(cur);
                            c.add(Calendar.DATE, 1);
                            fc.setLearnNextdate(c.getTime());
                            break;
                        case Flashcard.LEARNLEVEL_0:
                            fc.setLearnLevel(Flashcard.LEARNLEVEL_7);
                            c.setTime(cur);
                            c.add(Calendar.DATE, 7);
                            fc.setLearnNextdate(c.getTime());
                            break;
                        case Flashcard.LEARNLEVEL_1:
                        default:
                            fc.setLearnLevel(Flashcard.LEARNLEVEL_FINISH);
                            fc.setLearnNextdate(null);
                            break;
                        }
                    }
                    testcase.setMakeupPoint(true);
                } else {
                    Calendar c = Calendar.getInstance();
                    Date cur = new Date();
                    int dt = 0;
                    switch (fc.getLearnLevel()) {
                    case Flashcard.LEARNLEVEL_0:
                        fc.setLearnLevel(Flashcard.LEARNLEVEL_1);
                        c.setTime(cur);
                        c.add(Calendar.DATE, 1);
                        fc.setLearnNextdate(c.getTime());
                        if (fc.getTestresult0() > 0)
                            testcase.setMakeupPoint(true);
                        dt = testcase.getResultscore() - fc.getTestresult0();
                        if (dt > 0) {
                            fc.setTestresult0(testcase.getResultscore());
                            testcase.setResultscore(dt);
                        } else
                            testcase.setResultscore(0);

                        user.setKnowCards0(user.getKnowCards0() + 1);
                        break;
                    case Flashcard.LEARNLEVEL_1:
                        fc.setLearnLevel(Flashcard.LEARNLEVEL_7);
                        c.setTime(cur);
                        c.add(Calendar.DATE, 7);
                        fc.setLearnNextdate(c.getTime());
                        if (fc.getTestresult1() > 0)
                            testcase.setMakeupPoint(true);
                        dt = testcase.getResultscore() - fc.getTestresult1();
                        if (dt > 0) {
                            fc.setTestresult1(testcase.getResultscore());
                            testcase.setResultscore(dt);
                        } else
                            testcase.setResultscore(0);

                        user.setKnowCards1(user.getKnowCards1() + 1);
                        break;
                    case Flashcard.LEARNLEVEL_7_1:
                    case Flashcard.LEARNLEVEL_7:
                        fc.setLearnLevel(Flashcard.LEARNLEVEL_FINISH);
                        fc.setLearnNextdate(null);

                        if (fc.getTestresult7() > 0) {// already test
                            dt = testcase.getResultscore() - fc.getTestresult7();
                            if (dt > 0)
                                fc.setTestresult7(testcase.getResulttime());
                            testcase.setResultscore(0);
                            testcase.setMakeupPoint(true);
                        } else {
                            fc.setTestresult7(testcase.getResulttime());
                            user.setKnowCards7(user.getKnowCards7() + 1);
                        }
                        break;
                    }
                }
            }

            fc.setTestStatus(Flashcard.TESTSTATUS_RIGHT);
            fc.setLasttestdate(new Date());
            fc.setLasttesttype(testcase.getTesttype());

            EcommerceService esrv = Repository.getInstance().getEcommerceService();

            esrv.applyPromotions(user, Promotion.TARGETTYPE_USER, Promotion.CONDITIONTYPE_KNOWWORDS_L0, user.getKnowCards0(), true);
            esrv.applyPromotions(user, Promotion.TARGETTYPE_USER, Promotion.CONDITIONTYPE_KNOWWORDS_L1, user.getKnowCards1(), true);
            esrv.applyPromotions(user, Promotion.TARGETTYPE_USER, Promotion.CONDITIONTYPE_KNOWWORDS_L7, user.getKnowCards7(), true);

            usrv.save(user);
            srv.save(fc);
        }
    }

    /**
     * A flashcard has fail the test
     * 
     * @param flashcardid
     * @param isTestAll
     *            is in test all mode
     */
    public void setFlashcardFailTest(Long flashcardid, boolean isTestAll, Testcase testcase) {
        if (isTestAll)
            return;

        FlashcardService srv = Repository.getInstance().getFlashcardService();
        Flashcard fc = srv.findByID(flashcardid);
        if (fc != null) {
            // update user status
            UserService usrv = Repository.getInstance().getUserService();
            User user = Utility.getUser();
            int know = user.getKnowCards();
            int unknow = user.getUnknowCards();
            int nc = user.getNewCards();
            // TODO check this
            if (fc.getTestStatus() == Flashcard.TESTSTATUS_NOTYET) {
                nc -= 1;
                unknow += 1;
            } else if (fc.getTestStatus() == Flashcard.TESTSTATUS_RIGHT) {
                know -= 1;
                unknow += 1;
            }

            user.setKnowCards(know > 0 ? know : 0);
            user.setUnknowCards(unknow > 0 ? unknow : 0);
            user.setNewCards(nc > 0 ? nc : 0);

            Calendar c = Calendar.getInstance();
            Date cur = new Date();
            int lastTestStatus = fc.getTestStatus();
            if (testcase.isSameDate()) {
                if (lastTestStatus == Flashcard.TESTSTATUS_RIGHT) {
                    c.setTime(cur);
                    c.add(Calendar.DATE, 1);
                    fc.setLearnNextdate(c.getTime());

                    switch (fc.getLearnLevel()) {
                    case Flashcard.LEARNLEVEL_FINISH:
                        fc.setLearnLevel(Flashcard.LEARNLEVEL_7_1);
                        break;
                    case Flashcard.LEARNLEVEL_7:
                        fc.setLearnLevel(Flashcard.LEARNLEVEL_0);
                        break;
                    case Flashcard.LEARNLEVEL_1:
                    default:
                        fc.setLearnLevel(Flashcard.LEARNLEVEL_NEW);
                        fc.setLearnNextdate(null);
                        break;
                    }
                }
            } else {
                switch (fc.getLearnLevel()) {
                case Flashcard.LEARNLEVEL_0:
                    fc.setLearnLevel(Flashcard.LEARNLEVEL_NEW);
                    fc.setLearnNextdate(null);
                    break;
                case Flashcard.LEARNLEVEL_1:
                    fc.setLearnLevel(Flashcard.LEARNLEVEL_0);
                    c.setTime(cur);
                    c.add(Calendar.DATE, 1);
                    fc.setLearnNextdate(c.getTime());

                    user.setKnowCards0(user.getKnowCards0() - 1);
                    break;
                case Flashcard.LEARNLEVEL_7_1:
                    fc.setLearnLevel(Flashcard.LEARNLEVEL_1);
                    c.setTime(cur);
                    c.add(Calendar.DATE, 1);
                    fc.setLearnNextdate(c.getTime());

                    user.setKnowCards1(user.getKnowCards1() - 1);
                    break;
                case Flashcard.LEARNLEVEL_7:
                    fc.setLearnLevel(Flashcard.LEARNLEVEL_7_1);
                    c.setTime(cur);
                    c.add(Calendar.DATE, 1);
                    fc.setLearnNextdate(c.getTime());
                    break;
                }
            }

            fc.setTestStatus(Flashcard.TESTSTATUS_FAIL);
            fc.setLasttestdate(new Date());
            fc.setLasttesttype(testcase.getTesttype());

            usrv.save(user);
            srv.save(fc);
        }
    }

    /**
     * Save medal data to database
     * 
     * @param medal
     */
    public void saveMedal(Medal medal) {
        getMedalDAO().save(medal);
    }

    /**
     * Delete a medal from system, include images and history
     * 
     * @param medal
     */
    public void deleteMedal(Medal medal) {
        if (medal == null)
            return;
        /*
         * Repository.getInstance().deleteBinaryData(medal.getLargeThumbnail());
         * Repository.getInstance().deleteBinaryData(medal.getSmallThumbnail());
         * Repository.getInstance().deleteBinaryData(medal.getFocusThumbnail());
         */

        List<MedalHistory> lst = getMedalHistories(medal);
        if (lst != null && lst.size() > 0) {
            for (MedalHistory h : lst)
                deleteMedalhistory(h);
        }

        getMedalDAO().delete(medal);
    }

    /**
     * Get medal data
     * 
     * @param id
     * @return
     */
    public Medal getMedal(String id) {
        if (Utility.isEmptyString(id))
            return null;
        return getMedalDAO().get(new ObjectId(id));
    }

    /**
     * Save medalhistory data
     * 
     * @param history
     */
    public void saveMedalhistory(MedalHistory history) {
        getMedalhistoryDAO().save(history);
    }

    /**
     * Delete a medalhistory
     * 
     * @param history
     */
    public void deleteMedalhistory(MedalHistory history) {
        getMedalhistoryDAO().delete(history);
    }

    /**
     * Get medal history data
     * 
     * @param id
     * @return
     */
    public MedalHistory getMedalhistory(String id) {
        return getMedalhistoryDAO().get(new ObjectId(id));
    }

    /**
     * Get medal history of user
     * 
     * @param medal
     * @return
     */
    public MedalHistory getMedalhistory(Long userid, Medal medal) {
        BasicDAO<MedalHistory, ObjectId> dao = getMedalhistoryDAO();
        Query<MedalHistory> query = dao.createQuery();
        query.field("medal").equal(medal);
        query.field("userid").equal(userid);
        return query.get();
    }

    /**
     * Get list history of a medal
     * 
     * @param medal
     * @return
     */
    public List<MedalHistory> getMedalHistories(Medal medal) {
        BasicDAO<MedalHistory, ObjectId> dao = getMedalhistoryDAO();
        Query<MedalHistory> query = dao.createQuery();
        query.field("medal").equal(medal);
        return query.asList();
    }

    /**
     * Get list of gained medal of user
     * 
     * @param userid
     * @return
     */
    public List<Medal> getMedalsOfUser(Long userid) {
        BasicDAO<MedalHistory, ObjectId> dao = getMedalhistoryDAO();
        Query<MedalHistory> query = dao.createQuery();
        query.field("userid").equal(userid);
        List<Medal> result = new ArrayList<Medal>();
        for (MedalHistory h : query.fetch())
            result.add(h.getMedal());
        return result;
    }

    /**
     * Count number of medals of a user
     * 
     * @param userid
     * @return
     */
    public int countMedalsOfUser(Long userid) {
        BasicDAO<MedalHistory, ObjectId> dao = getMedalhistoryDAO();
        Query<MedalHistory> query = dao.createQuery();
        query.field("userid").equal(userid);
        return (int) query.countAll();
    }

    /**
     * Check to see if the user got medal or not
     * 
     * @param userid
     * @param medal
     * @return
     */
    public boolean checkUserGotMedal(Long userid, Medal medal) {
        BasicDAO<MedalHistory, ObjectId> dao = getMedalhistoryDAO();
        Query<MedalHistory> query = dao.createQuery();
        query.field("userid").equal(userid);
        query.field("medal").equal(medal);
        return query.countAll() > 0l;
    }

    /**
     * Count the percent of user that gained this medal
     * 
     * @param medal
     * @return
     */
    public double countPercentUsersGainedMedal(Medal medal) {
        BasicDAO<MedalHistory, ObjectId> hisdao = getMedalhistoryDAO();
        Query<MedalHistory> query = hisdao.createQuery();
        query.field("medal").equal(medal);
        long all = query.countAll();

        UserService usrv = Repository.getInstance().getUserService();
        Long alluser = usrv.countNumberOfAllUsers();

        double count = Math.round(all * 1000 / alluser) / 10;

        return count < 0.1 ? 0.1 : count;
    }

    /**
     * Count the percent of user that gained this medal
     * 
     * @param medalid
     * @return
     */
    public double countPercentUsersGainedMedal(String medalid) {
        Medal medal = getMedal(medalid);
        if (medal == null)
            return 0;

        return countPercentUsersGainedMedal(medal);
    }

    /**
     * Get all medals in the system
     * 
     * @return
     */
    public List<Medal> getMedals() {
        BasicDAO<Medal, ObjectId> dao = getMedalDAO();
        Query<Medal> query = dao.createQuery();
        query.order("weight");
        query.order("name");
        return query.asList();
    }

    /**
     * Get list of medals by category
     * 
     * @param category
     * @return
     */
    public List<Medal> getMedalsByCategory(int category) {
        BasicDAO<Medal, ObjectId> dao = getMedalDAO();
        Query<Medal> query = dao.createQuery();
        query.field("category").equal(category);
        query.field("visible").equal(1);
        query.order("weight");
        query.order("name");
        return query.asList();
    }

    /**
     * Find a medal by it's name
     * 
     * @param name
     * @return
     */
    public Medal getMedalByName(String name) {
        BasicDAO<Medal, ObjectId> dao = getMedalDAO();
        Query<Medal> query = dao.createQuery();
        query.field("name").equal(name);
        return query.get();
    }

    /**
     * User gets a medal
     * 
     * @param user
     * @param medal
     */
    public void userGetMedal(User user, Medal medal) {
        if (medal == null) {
            // TODO log this case
            return;
        }
        if (checkUserGotMedal(user.getId(), medal))
            return;
        BasicDAO<MedalHistory, ObjectId> udao = getMedalhistoryDAO();
        MedalHistory his = new MedalHistory();
        his.setMedal(medal);
        his.setUserid(user.getId());
        udao.save(his);
        // add activity
        UserService usrv = Repository.getInstance().getUserService();
        usrv.addActivityGetMedal(user, medal);
        // add notification
        NotificationService nsrv = Repository.getInstance().getNotificationService();
        nsrv.addNotificationGetMedal(user, medal);
    }

    /**
     * Return flashcardsets of user order by learned date
     * 
     * @param user
     * @param page
     *            Current page
     * @param pagesize
     *            The page size
     * @return
     */
    public List<FlashcardSet> getLastTestedFlashcardsets(User user, int page, int pagesize) {
        org.hibernate.Query query = Repository
                .getInstance()
                .getCurrentSession()
                .createQuery(
                        "SELECT fcs FROM FlashcardSet fcs, Flashcard fc WHERE fc.flashcardSet=fcs and fcs.author=:user GROUP BY fcs ORDER BY max(fc.lasttestdate) DESC");
        query.setParameter("user", user);
        query.setMaxResults(pagesize);
        query.setFirstResult(page * pagesize);
        return query.list();
    }

    /**
     * Return flashcardsets of user order by learned date
     * 
     * @param user
     * @param page
     *            Current page
     * @param pagesize
     *            The page size
     * @return
     */
    public int countLastTestedFlashcardsets(User user) {
        FlashcardSetService srv = Repository.getInstance().getFlashcardSetService();
        Criteria crit = srv.createCriteria();
        crit.add(Restrictions.eq("author", user));
        crit.setProjection(Projections.rowCount());
        crit.add(Restrictions.isNotEmpty("flashcards"));
        return (Integer) crit.uniqueResult();
    }

}
